%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}

\newcommand{\dnsFigs}{./Figs}
\lhead{\bfseries SEED Labs -- DNS In a Box}

\def \code#1 {\fbox{\scriptsize{\texttt{#1}}}}

\newcommand{\bankcom}{\url{bank32.com}\xspace}
\newcommand{\wwwbank}{\url{www.bank32.com}\xspace}
\newcommand{\examplenet}{\url{example.net}\xspace}
\newcommand{\wwwexample}{\url{www.example.net}\xspace}
\newcommand{\dockerfile}{\texttt{Dockerfile}\xspace}
\newcommand{\bind}{\texttt{BIND9}\xspace}

\begin{document}

\begin{center}
{\LARGE SEED Lab: DNS In a Box}
\end{center}

\seedlabcopyright{2020}


% *******************************************
% SECTION
% ******************************************* 
\section{实验概述}

DNS（域名系统）是互联网的电话簿，它将主机名转换为IP地址（反之亦然）。这个转换过程通过DNS解析完成，这个过程在后台进行。该解析过程涉及多个名称服务器，包括根服务器、顶级域（TLD）服务器和最终的域名服务器。这些名称服务器组成了整个DNS系统，是互联网的重要基础设施。

为了帮助学生理解这些名称服务器如何一起工作以形成基础设施，我们将创建一个名为“DNS In a Box”的小型DNS系统。正如其名称所示，该DNS系统中的多个名称服务器运行在一个单一机器上。这是通过容器技术实现的。

尽管这个系统很小，但它包含了真实DNS基础设施的所有基本元素。通过构建这样的系统，学生将更深入地了解DNS是如何工作的。虽然这不是一个安全实验，但它是几个SEED实验的基础。本实验涵盖以下主题：

\begin{itemize}[noitemsep]
    \item DNS及其工作原理
    \item DNS查询过程
    \item 根服务器和顶级域（TLD）服务器
    \item Docker容器、Docker Compose
\end{itemize}

\paragraph{参考资料。}
关于DNS协议的详细内容可以在以下资源中找到：

\begin{itemize}
    \item 《SEED书》第18章，\seedbook
    \item 《SEED讲座》第7节，\seedisvideo
\end{itemize}

\paragraph{实验环境。}
\seedenvironmentB
\nodependency


% *******************************************
% SECTION
% ******************************************* 
\section{实验准备} 

\begin{figure}[htb]
\begin{center}
\includegraphics[width=0.95\textwidth]{Figs/DNS-in-a-box.pdf}
\end{center}
\caption{简化版的DNS基础设施}
\label{dns:fig:dns-in-a-box}
\end{figure}

在本实验中，我们将构建一个简化的DNS基础设施。我们将从小规模开始构建，然后逐渐扩大。我们建立的第一个DNS系统由四个名称服务器组成，每个名称服务器代表DNS基础设施中的特定角色。在现实世界中，这些名称服务器分布在不同的网络上，但在本实验中，为了简化操作，我们将它们放置在同一网络中。图~\ref{dns:fig:dns-in-a-box}描述了该系统的配置。我们将使用容器来运行这些名称服务器。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{容器的设置和命令}

`````
\input{\commonfolder/container/setup}
````

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{Docker Compose文件} 

我们的DNS系统包括四个名称服务器。这些名称服务器的作用总结如下：

\begin{itemize}[nosep]
    \item 本地DNS服务器：为其他计算机进行DNS解析。
    \item 根服务器：提供根区域的服务的名称服务器。
    \item \texttt{edu}服务器：提供.edu区域的服务，这是一个顶级域（TLD）。
    \item \texttt{example.edu}服务器：提供\texttt{example.edu}区域的服务。
\end{itemize}


我们为每个名称服务器都使用了一个容器。
这些容器是如何构建和运行的将在\texttt{docker-compose.yml}文件中描述，该文件包含在实验配置文件中。在这个文件中，我们创建了一个网络 \texttt{10.9.0.0/24} 以及四个容器：每个 \texttt{services} 部分中的条目代表一个容器，并且为这些容器分配了IP地址。以下是一个示例片段：

\begin{lstlisting}
services:
  seed_base_router:                       (*@\ding{80}@*)
    build: ./base_image
    image: seed-base-image-bind
    container_name: seed-base-bind
    command: " echo 'exiting ...' "

  example_edu_server:
    build:
        context: ./nameserver
        args:
            BIND_CONF_DIR: edu.example
    image: example-edu-server
    container_name: example-edu-10.9.0.65
    tty: true
    networks:
      seed-net:
        ipv4_address: 10.9.0.65

  edu_server:        ... (省略，与example_edu_server相似) ...
        ipv4_address: 10.9.0.60
  root_server:       ... (省略，与example_edu_server相似) ...
        ipv4_address: 10.9.0.30
  local_dns_server:  ... (省略，与example_edu_server相似) ...
        ipv4_address: 10.9.0.53
\end{lstlisting}

行 \ding{80} 指定了一个基础镜像，基本上是基于我们放置在Docker Hub上的\path{handsonsecurity/seed-server:bind}镜像构建的（见 \texttt{base\_image} 文件夹中的\texttt{Dockerfile}）。虽然所有名称服务器可以直接从Docker Hub中使用这个基础镜像来构建它们自己的容器，但为了减少对Docker Hub的访问次数（公司规定每六小时只能进行一定数量的访问），我们首先在本地构建一个基于Docker Hub上的该基础镜像的本地基础镜像，然后使用该本地基础镜像来构建本实验中的其他容器镜像。


% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{容器镜像} 

除了本地DNS服务器外，所有名称服务器都使用相同的文件夹（\texttt{nameserver}）来构建Docker镜像。以下是\texttt{Dockerfile}的内容：

\begin{lstlisting}
FROM seed-base-image-bind                         (*@\ding{202}@*)

ARG BIND_CONF_DIR

# Copy the BIND configuration files
COPY named.conf named.conf.options  /etc/bind/    (*@\ding{203}@*)
COPY ${BIND_CONF_DIR}     /etc/bind/              (*@\ding{204}@*)

# Start the nameserver
CMD service named start  && tail -f /dev/null
\end{lstlisting}

行 \ding{202} 指明这是一个本地镜像（基础镜像）。行 \ding{203} 将 BIND 9 的配置文件复制到容器内的 \texttt{/etc/bind} 文件夹中。这两个文件对所有名称服务器都是一样的，但每个名称服务器都有自己的区域文件。这就是为什么我们使用了 \texttt{BIND\_CONF\_DIR} 参数（行 \ding{204}）来指定应该使用的配置文件夹（每个名称服务器都有一个独立的目录）。\texttt{BIND\_CONF\_DIR} 的值在 Compose 文件中设置。

\paragraph{本地DNS服务器。}
本地DNS服务器的容器镜像与名称服务器的非常相似。我们稍后会解释其中的不同，因为构建该镜像是一个任务的一部分。

% *******************************************
% SECTION
% *******************************************
\section{任务1：为 \texttt{example.edu} 域建立名称服务器}

在这个任务中，我们将为名为 \texttt{example.edu} 的域建立一个名称服务器。容器文件位于 \texttt{nameserver} 文件夹内。
特定域的配置文件位于 \path{edu.example} 子文件夹中。

\paragraph{步骤1. 添加区域条目}. 要为主机提供 \texttt{example.edu} 域服务，我们需要在 BIND 9 的配置文件 \texttt{named.conf} 中添加一个区域条目，以便名称服务器知道自己将要管理哪些域。为了方便起见，我们将这个条目添加到我们修改后的 \texttt{named.conf} 文件中的 \path{named.conf.seedlabs}。

此条目表示当前名称服务器是该域的主服务器，并且指定区域文件在 \texttt{file} 条目中。

\begin{lstlisting}
zone "example.edu" {
        type master;
        file "/etc/bind/example.edu.db";
};
\end{lstlisting}

\paragraph{步骤2. 创建区域文件}. 在构建 Docker 镜像时，区域文件 \path{example.edu.db} 将被复制到容器内的 \path{/etc/bind} 文件夹中。为了让学生开始操作，我们在该区域文件中提供了一些信息，但学生需要进行所有必要的更改。

\begin{lstlisting}
$TTL 3D
@       IN      SOA   ns.example.edu. admin.example.edu. (
                2008111001
                8H
                2H
                4W
                1D)

; Records for this nameserver (you need to make changes)
@               IN   NS    ns.example.edu.
ns.example.edu  IN   A     1.2.3.4


; IP addresses for the hostnames in the example.du domain
@       IN      A     1.2.3.5
www     IN      A     1.2.3.5
xyz     IN      A     1.2.3.6
*       IN      A     1.2.3.7
\end{lstlisting}

\paragraph{步骤3. 测试}. 使用 \texttt{docker-compose} 命令来构建并启动所有容器。一旦容器运行，在你的虚拟机（即容器外部）运行以下命令。我们使用 \texttt{@10.9.0.65} 选项将DNS查询直接发送到文本中指定的IP地址 \texttt{10.9.0.65}，这是我们在设置中的\texttt{example.edu}名称服务器的IP地址。如果一切操作正确，则可以获取你在区域文件中指定的IP地址。请报告你的观察结果。


\begin{lstlisting}
$ dig @10.9.0.65 www.example.edu
... 
;; ANSWER SECTION:
www.example.edu.    259200   IN  A   (*@\textbf{1.2.3.5}@*)
...
\end{lstlisting}


% *******************************************
% SECTION
% *******************************************
\section{任务2：建立 \texttt{edu} TLD名称服务器}

在这个任务中，我们将为顶级域（TLD）域名构建一个名称服务器，即 \texttt{edu} 域名。我们需要修改 \texttt{nameserver/edu} 文件夹中的文件。
首先，我们需向 \path{named.conf.seedlabs} 文件添加以下区域条目。此条目表示当前的名称服务器是该域的主服务器，并且指定区域文件在 \texttt{file} 条目中。

\begin{lstlisting}
zone "edu" {
        type master;
        file "/etc/bind/edu.db";
};
\end{lstlisting}

\paragraph{创建区域文件}. 接下来，我们需要向区域文件添加记录。我们已经创建了区域文件 \texttt{edu.db}，但它不完整。学生需要进行所有必要的更改。

所有属于 \texttt{edu} 域的名称服务器都必须将其自己的名称服务器注册到这个顶级域（TLD）名称服务器中；否则，没有人能够找到它们。例如，我们为 \texttt{syr.edu} 域添加了两个记录：一个 \texttt{NS} 记录和一个 \texttt{A} 记录。
\texttt{NS} 记录指定了 \texttt{syr.edu} 域的名称服务器，而 \texttt{A} 记录则指出了名称服务器的IP地址。这些记录中的数据是真实的，因为它们模拟了 \texttt{syr.edu} 域已经将其自身注册到了我们的 \texttt{edu} TLD 名称服务器上的事实。

\begin{lstlisting}
; Real records for syr.edu
syr.edu.                IN      NS      ns1.syr.edu.
ns1.syr.edu.            IN      A       128.230.12.8
\end{lstlisting}

\paragraph{实验室任务}. 学生需要完成以下任务：（1）将你的 \texttt{example.edu} 名称服务器注册到此顶级域名称服务器中；（2）选择三个真实的域名，并将其注册到这个顶级域名称服务器。你可以通过运行 \texttt{"dig <name> NS"} 命令来查找一个域的名称服务器（例如，\texttt{"dig syr.edu NS"})。

使用 \texttt{docker-compose} 命令，学生可以构建并启动所有容器。一旦容器运行，
从你的虚拟机发送 DNS 查询到 \texttt{edu} 名称服务器（其 IP 地址在我们的设置中为 \texttt{10.9.0.60})。请报告你的观察结果。

\begin{lstlisting}
$ dig @10.9.0.60 www.example.edu
$ dig @10.9.0.60 www.syr.edu
$ dig @10.9.0.60 <你选择的其他三个域名>
$ dig @10.9.0.60 <不在你的区域文件中的某个域名>
\end{lstlisting}

需要注意的是，\texttt{edu} TLD 名称服务器配置为不执行递归查询，因此它只会告诉你查询中域名所对应的名称服务器；它不会为你解决这个查询。在最后一个命令中，你应该尝试一些以 \texttt{edu} 结尾但不在你的区域文件中的域名。

% *******************************************
% SECTION
% *******************************************
\section{任务4：建立根名称服务器}

在这个任务中，我们将为根域建立一个名称服务器。所有顶级域名（TLD）名称服务器都必须向根名称服务器注册，以便它们能在DNS查询过程中被找到。
在我们的容器中，根域的定义位于 \texttt{bind.conf.seedlabs} 文件中，并从区域文件 \texttt{root.db} 中加载记录。

对于我们希望包含在微型DNS系统中的每个顶级域（TLD），我们需要在区域文件中至少添加两条记录，包括一个 \texttt{NS} 记录和一个 \texttt{A} 记录。以下是一个例子，在该示例中，我们为 \texttt{com} 和 \texttt{net} 域添加了名称服务器信息（这些记录中的数据是真实的）。这两个域由同一家公司管理，因此它们共享相同的名称服务器集（我们仅包含了一个名称服务器）。

\begin{lstlisting}
com.                 IN   NS   a.gtld-servers.net.
net.                 IN   NS   a.gtld-servers.net.
a.gtld-servers.net.  IN   A    192.5.6.30
\end{lstlisting}

\paragraph{实验室任务}. 学生需要向 \texttt{root.db} 区域文件中添加记录，以满足以下要求：

- 将你的 \texttt{edu} 名称服务器注册到这个根名称服务器。
- 选择两个其他真实的顶级域名，并将它们也注册到此 TLD 名称服务器。其中一个顶级域（TLD）应是一个国家代码顶级域（ccTLD），它是某个国家的根。你可以通过运行 \texttt{"dig <tld> NS"} 命令来查找一个顶级域名称服务器的信息（例如，\texttt{"dig com NS"})。你也可以从以下网址找到所有顶级域名信息：https://www.internic.net/domain/root.zone。

使用 \texttt{docker-compose} 命令，学生可以构建并启动所有容器。
一旦容器运行，
从你的虚拟机发送 DNS 查询到根名称服务器（通过 \texttt{"dig @10.9.0.30"} 命令）来测试是否你的 DNS 设置按预期工作。

% *******************************************
% SECTION
% *******************************************
\section{任务5：建立本地DNS服务器}

当计算机需要将主机名转换为IP地址（反之亦然）时，它会向其辅助器发送一个请求。这个辅助器可以不在附近。本地DNS服务器将会进行整个DNS解析过程，并将结果反馈给计算机。
在这个任务中，我们将设置这个本地DNS服务器。

\paragraph{根提示文件}. 如果本地DNS服务器无法从缓存中找到答案，则它将继续通过迭代过程从外部名称服务器获取答案。这一过程始于根服务器。因此，本地DNS服务器需要知道根服务器的IP地址。

在 \path{/etc/bind/named.conf.default-zones} 中（这是 \bind 的配置文件 \path{/etc/bind/named.conf} 中的一部分），有一个针对根域的条目。这个条目为根区域指定了一个提示文件，本地DNS服务器通过这个文件知道根服务器的IP地址。

\begin{lstlisting}
zone "." {
    type hint;
    file "/usr/share/dns/root.hints";
};
\end{lstlisting}

根提示文件指定了根区中名称服务器的信息（根域有13个名称服务器）。这些名称服务器的IPv4和IPv6地址都在这个文件中提供。以下是一个示例片段。

\begin{lstlisting}
.                        3600000      NS    A.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET.      3600000      A     198.41.0.4
A.ROOT-SERVERS.NET.      3600000      AAAA  2001:503:ba3e::2:30
...
\end{lstlisting}

在我们实验配置文件中的 \texttt{local\_dns\_server} 文件夹内，我们创建了一个名为 \texttt{root.hints} 的空文件。当我们为本地DNS服务器构建容器镜像时，这个文件将被复制到 \path{/usr/share/dns/} 文件夹中。你需要在该文件中添加记录，以便你的本地DNS服务器可以使用你的根服务器，而不是使用真实世界中的服务器。

\paragraph{测试}. 使用 \texttt{docker-compose} 命令来构建并启动所有容器。
一旦容器运行，
从你的虚拟机发送以下 DNS 查询命令（其 IP 地址在我们的设置中为 \texttt{10.9.0.53}）到本地DNS名称服务器。

\begin{lstlisting}
$ dig @10.9.0.53 www.example.edu
$ dig @10.9.0.53 www.example.com
$ dig @10.9.0.53 www.example.net
$ dig @10.9.0.53 www.syr.edu
$ dig @10.9.0.53 www.xyz.<注册在你的根服务器中的TLD>
$ dig @10.9.0.53 www.xyz.<未注册在你的根服务器中的TLD>
\end{lstlisting}

% *******************************************
% SECTION
% *******************************************
\section{任务6：配置虚拟机以使用这个本地DNS服务器} 

到目前为止，我们在 \texttt{dig} 命令中需要使用 \texttt{@<ip>} 来指定 \texttt{dig} 应该与哪个 DNS 服务器通信。虽然这对 \texttt{dig} 没有问题，但对于依赖 DNS 的其他软件可能会出现问题。我们需要告诉操作系统我们在这个任务中构建的DNS服务器容器是系统的本地DNS服务器。

这可以通过更改用户机器上的解析器配置文件（\texttt{/etc/resolv.conf}），将该容器的 IP 地址作为第一个 \texttt{nameserver} 条目添加到文件中来实现，即这个服务器将被用作首选 DNS 服务器。
不幸的是，我们提供的虚拟机使用动态主机配置协议（DHCP）获取网络参数，如IP地址、本地DNS服务器等。DHCP客户端会覆盖 \texttt{/etc/resolv.conf} 文件以包含来自 DHCP 服务器的信息。

为了在不担心 DHCP 的情况下将我们的信息添加到 \texttt{/etc/resolv.conf} 中，可以在路径 \path{/etc/resolvconf/resolv.conf.d/head} 添加以下条目：

\begin{lstlisting}
Add the following entry to /etc/resolvconf/resolv.conf.d/head
  nameserver 10.9.0.53

Run the following command for the change to take effect
$ sudo resolvconf -u
\end{lstlisting}

文件的内容将被添加到动态生成的解析器配置文件的开头。通常，这是只是一个注释行（在 \texttt{/etc/resolv.conf} 中的注释来自于这个头文件）。

完成用户机器的配置后，请重复任务5中的测试命令，但这次不需要包含 \texttt{@<ip>}。
注意高亮显示的IP地址以检查响应是否来自你的容器。如果设置不成功，则IP地址会不同。

\begin{lstlisting}
$ dig abc.example.edu

...
;; ANSWER SECTION:
abc.example.edu.	259200	IN	A	1.2.3.7

;; Query time: 3 msec
;; SERVER: (*@\textbf{10.9.0.53}@*)#53(10.9.0.53)
...
\end{lstlisting}

\paragraph{\ding{72}\ding{72}注意（非常重要）\ding{72}\ding{72}.} 完成实验后，请务必从 \path{/etc/resolvconf/resolv.conf.d/head} 中删除添加的条目，否则这将对未来实验造成许多问题。如果你将来在连接互联网时遇到麻烦，但你的网络是正常的，则可能是你忘记了删除这个条目，它破坏了DNS查询过程。我经常遇到这种情况。

% *******************************************
% SECTION
% ******************************************* 
\section{任务7：使其更接近现实} 

在这个任务中，我们将通过为我们的微型DNS系统添加以下名称服务器或记录来使其更加真实：

- 添加2个更多的根服务器。实际世界中的DNS系统有13个不同的根服务器的IP地址，在这个实验室中我们创建3个。

- 选择你root服务器注册的两个TLD，并分别为它们建立二级域名，使用你的姓氏作为域名称。例如，如果你的姓氏是 \texttt{Smith}，你需要为 \texttt{smith.<tld1>} 和 \texttt{smith.<tld2>} 域构建名称服务器。

将这两个域名都托管在同一名称服务器上。


%注意：尽量避免使用\texttt{com}和\texttt{net}。
%因为它们互相依赖，所以会更难。



% *******************************************
% SECTION
% ******************************************* 
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\paragraph{清理}. 这是另一个关于从 \path{/etc/resolvconf/resolv.conf.d/head} 中删除添加条目的提醒。

\end{document}
